import io
import sys
import chardet
from typing import Annotated, List
from fastapi import Depends, Header, Query, Form, Body, File, UploadFile, APIRouter
from fastapi.responses import Response
from utils.log_util import logger as log
from minio.commonconfig import Tags, CopySource, REPLACE
from utils.utility import json_result_reps
from .services import OssBaseHandler
from config.env import MinioSettings as minioCfg
from minio.error import MinioException
from fastapi_class import View, endpoint
from utils.log_util import logger as log
from config.env import MinioCfg


router = APIRouter()



def get_oss_handler(token: Annotated[str | None, Header(alias="X-Access-Token")] = None,
                    user_agent: Annotated[str | None, Header()] = None,
                    bucket_name: str = Query(alias="bucketName", default='contract'),
                    dirname: str = Query(alias="bizPath", default=''),
                    ):
    log.info("token", token)
    log.info("user_agent", user_agent)
    oss = OssBaseHandler(dirname, bucket_name)
    try:
        yield oss
    finally:
        del oss

class CommonQueryParams:
    def __init__(self, *,
                 filename: str = Form(default=None) or Query(default=None),
                 filePath: str = Form(default=None) or Query(default=None),
                 ):
        self.filename = filename
        self.filePath = filePath



@View(router)
class OssClass:

    def __updateMemo(self, object_name, metadata, oss:OssBaseHandler):
        try:
            tmp_object_name = object_name + "_update_metadata_tmp"
            tmp_obj_res = oss.client.copy_object(
                oss.bucket_name, tmp_object_name, CopySource(oss.bucket_name, object_name))
            print("finish copying to tmp", oss.bucket_name,
                  object_name + "->" + tmp_object_name, tmp_obj_res)
            print("begin copying tmp to origin with additional metadata")
            org_obj_res = oss.client.copy_object(
                oss.bucket_name, object_name,
                CopySource(oss.bucket_name, tmp_object_name),
                metadata=metadata, metadata_directive=REPLACE)
            print(org_obj_res)
            oss.client.remove_object(oss.bucket_name, tmp_object_name)
            print("remove tmp-obj")
        except Exception as e:
            print(e)
            oss.result['code'] = 500
            oss.result['success'] = False
            oss.result['message'] = f'error: {e}'
        return oss.result


    @endpoint(path="", methods=["GET"])
    async def get_item(self, oss:OssBaseHandler = Depends(get_oss_handler)):
        return json_result_reps(
            data=oss.listObjects()
        )

    @endpoint(path="list", methods=["GET"])
    async def get_item_non_recursive(self, oss:OssBaseHandler = Depends(get_oss_handler)):
        return json_result_reps(
            data=oss.listObjects(recursive=False)
        )

    @endpoint(path="", methods=["POST"])
    async def create_item(self, oss:OssBaseHandler = Depends(get_oss_handler), id_client: str = Form(default=None), memo: str = Form(default=''), config: str = Form(default=''), commons: CommonQueryParams = Depends(),
                          bucketName: str | None = Form(default=None), bizPath: str = Form(default=None), file: UploadFile = File(alias='file', default=None)):
        log.info("upload starting...")
        if bucketName:
            oss.bucket_name = bucketName
            oss.minioInit()
        oss.dirname = bizPath
        print(type(config))
        metadata = {"memo": memo.encode(), "config": config.encode(), "id_client": id_client}
        if file:
            filename, content_type = file.filename, file.content_type
            contents = await file.read()
            filesize = len(contents)
            encoding = chardet.detect(b"memo")
            log.info("encoding: {0}".format(encoding))
            log.info("size: {0} or {1}".format(sys.getsizeof(contents), filesize))
            if filesize > MinioCfg.minio_file_limit_size:
                oss.result['success'] = False
                oss.result['code'] = 403
                oss.result['message'] = "文件大小不能100M"
                return oss.result
            try:
                filePath = f'{oss.dirname}{filename}'
                log.info(filePath)
                stat = None
                try:
                    stat = oss.client.stat_object(oss.bucket_name, filePath)
                except MinioException as err:
                    log.info(err)
                if stat:
                    old_memo = stat.metadata.get('X-Amz-Meta-Memo', '')
                    old_config = stat.metadata.get('X-Amz-Meta-Config', '')
                    if not memo and old_memo:
                        metadata['memo'] = old_memo
                    if not config and old_config:
                        metadata['config'] = old_config
                    log.info(metadata)
                res = oss.client.put_object(
                    oss.bucket_name, filePath, io.BytesIO(contents), filesize,
                    content_type=content_type, metadata=metadata,
                )
                log.info(res)
            except Exception as e:
                oss.result['code'] = 500
                oss.result['success'] = False
                log.error(e)
        else:
            log.info("filePath:{0}, bucket_name:{1}".format(commons.filePath, oss.bucket_name))
            self.__updateMemo(commons.filePath, metadata, oss)
        return oss.result

    @endpoint(path="", methods=["DELETE"])
    async def delete_item(self, oss:OssBaseHandler = Depends(get_oss_handler),
                          filePath: str = Query(None), filePaths: List = Body(None,embed=True)):
        try:
            if filePath: oss.delFiles(filePath)
            if filePaths: oss.delFiles(filePaths)
            if not filePath and not filePaths:
                 raise Exception("filePath or filePaths is required")
        except Exception as e:
            print(e)
            oss.result['code'] = 500
            oss.result['success'] = False
        oss.result['message'] = 'deleted success!'
        return oss.result

    @endpoint(path="read", methods=["GET"])
    async def read_item(self, oss:OssBaseHandler = Depends(get_oss_handler), filePath: str = Query(alias="filePath")):
        res = oss.getObject(filePath, isDownload=False)
        log.info(res['header'])
        return Response(
            content=res['data'],
            headers={**res['header'], "Content-Disposition": 'inline; filename="xxx.pdf"'}
        )

    @endpoint(path="download", methods=["GET"])
    async def download_item(self, oss:OssBaseHandler = Depends(get_oss_handler), filepath: str = Query(alias="filePath")):
        res = oss.getObject(filepath)
        return Response(
            content=res['data'],
            headers=res['header']
        )

    @endpoint(path="tag", methods=["POST"])
    async def tag_item(self, oss:OssBaseHandler = Depends(get_oss_handler), memo: str = Body(embed=True), filePath: str = Body(embed=True)):
        try:
            log.info(f"memo: {memo}")
            if memo:
                tags = Tags.new_object_tags()
                tags["memo"] = memo
                tags["User"] = "jsmith"
                oss.client.set_object_tags(oss.bucket_name, filePath, tags)
        except Exception as e:
            oss.result['code'] = 500
            oss.result['success'] = False
            log.info(e)
